graphql 2.0.31 → 2.6.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (316) hide show
  1. checksums.yaml +4 -4
  2. data/lib/generators/graphql/detailed_trace_generator.rb +77 -0
  3. data/lib/generators/graphql/install/mutation_root_generator.rb +2 -2
  4. data/lib/generators/graphql/install/templates/base_mutation.erb +2 -0
  5. data/lib/generators/graphql/install/templates/mutation_type.erb +2 -0
  6. data/lib/generators/graphql/install_generator.rb +49 -0
  7. data/lib/generators/graphql/orm_mutations_base.rb +1 -1
  8. data/lib/generators/graphql/templates/base_argument.erb +2 -0
  9. data/lib/generators/graphql/templates/base_connection.erb +2 -0
  10. data/lib/generators/graphql/templates/base_edge.erb +2 -0
  11. data/lib/generators/graphql/templates/base_enum.erb +2 -0
  12. data/lib/generators/graphql/templates/base_field.erb +2 -0
  13. data/lib/generators/graphql/templates/base_input_object.erb +2 -0
  14. data/lib/generators/graphql/templates/base_interface.erb +2 -0
  15. data/lib/generators/graphql/templates/base_object.erb +2 -0
  16. data/lib/generators/graphql/templates/base_resolver.erb +8 -0
  17. data/lib/generators/graphql/templates/base_scalar.erb +2 -0
  18. data/lib/generators/graphql/templates/base_union.erb +2 -0
  19. data/lib/generators/graphql/templates/create_graphql_detailed_traces.erb +10 -0
  20. data/lib/generators/graphql/templates/graphql_controller.erb +2 -0
  21. data/lib/generators/graphql/templates/loader.erb +2 -0
  22. data/lib/generators/graphql/templates/mutation.erb +2 -0
  23. data/lib/generators/graphql/templates/node_type.erb +2 -0
  24. data/lib/generators/graphql/templates/query_type.erb +2 -0
  25. data/lib/generators/graphql/templates/schema.erb +5 -0
  26. data/lib/generators/graphql/type_generator.rb +1 -1
  27. data/lib/graphql/analysis/analyzer.rb +90 -0
  28. data/lib/graphql/analysis/field_usage.rb +82 -0
  29. data/lib/graphql/analysis/max_query_complexity.rb +20 -0
  30. data/lib/graphql/analysis/max_query_depth.rb +20 -0
  31. data/lib/graphql/analysis/query_complexity.rb +263 -0
  32. data/lib/graphql/analysis/query_depth.rb +58 -0
  33. data/lib/graphql/analysis/visitor.rb +280 -0
  34. data/lib/graphql/analysis.rb +102 -1
  35. data/lib/graphql/autoload.rb +38 -0
  36. data/lib/graphql/backtrace/table.rb +118 -55
  37. data/lib/graphql/backtrace.rb +1 -19
  38. data/lib/graphql/coercion_error.rb +1 -9
  39. data/lib/graphql/current.rb +57 -0
  40. data/lib/graphql/dashboard/application_controller.rb +41 -0
  41. data/lib/graphql/dashboard/detailed_traces.rb +47 -0
  42. data/lib/graphql/dashboard/installable.rb +22 -0
  43. data/lib/graphql/dashboard/landings_controller.rb +9 -0
  44. data/lib/graphql/dashboard/limiters.rb +93 -0
  45. data/lib/graphql/dashboard/operation_store.rb +199 -0
  46. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.css +6 -0
  47. data/lib/graphql/dashboard/statics/bootstrap-5.3.3.min.js +7 -0
  48. data/lib/graphql/dashboard/statics/charts.min.css +1 -0
  49. data/lib/graphql/dashboard/statics/dashboard.css +30 -0
  50. data/lib/graphql/dashboard/statics/dashboard.js +143 -0
  51. data/lib/graphql/dashboard/statics/header-icon.png +0 -0
  52. data/lib/graphql/dashboard/statics/icon.png +0 -0
  53. data/lib/graphql/dashboard/statics_controller.rb +31 -0
  54. data/lib/graphql/dashboard/subscriptions.rb +97 -0
  55. data/lib/graphql/dashboard/views/graphql/dashboard/detailed_traces/traces/index.html.erb +45 -0
  56. data/lib/graphql/dashboard/views/graphql/dashboard/landings/show.html.erb +18 -0
  57. data/lib/graphql/dashboard/views/graphql/dashboard/limiters/limiters/show.html.erb +62 -0
  58. data/lib/graphql/dashboard/views/graphql/dashboard/not_installed.html.erb +18 -0
  59. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/_form.html.erb +24 -0
  60. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/edit.html.erb +21 -0
  61. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/index.html.erb +69 -0
  62. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/clients/new.html.erb +7 -0
  63. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/index.html.erb +39 -0
  64. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/index_entries/show.html.erb +32 -0
  65. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/index.html.erb +81 -0
  66. data/lib/graphql/dashboard/views/graphql/dashboard/operation_store/operations/show.html.erb +71 -0
  67. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/subscriptions/show.html.erb +41 -0
  68. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/index.html.erb +55 -0
  69. data/lib/graphql/dashboard/views/graphql/dashboard/subscriptions/topics/show.html.erb +40 -0
  70. data/lib/graphql/dashboard/views/layouts/graphql/dashboard/application.html.erb +108 -0
  71. data/lib/graphql/dashboard.rb +96 -0
  72. data/lib/graphql/dataloader/active_record_association_source.rb +84 -0
  73. data/lib/graphql/dataloader/active_record_source.rb +47 -0
  74. data/lib/graphql/dataloader/async_dataloader.rb +112 -0
  75. data/lib/graphql/dataloader/null_dataloader.rb +55 -10
  76. data/lib/graphql/dataloader/request.rb +5 -0
  77. data/lib/graphql/dataloader/source.rb +35 -12
  78. data/lib/graphql/dataloader.rb +224 -149
  79. data/lib/graphql/date_encoding_error.rb +1 -1
  80. data/lib/graphql/dig.rb +2 -1
  81. data/lib/graphql/duration_encoding_error.rb +16 -0
  82. data/lib/graphql/execution/field_resolve_step.rb +631 -0
  83. data/lib/graphql/execution/finalize.rb +217 -0
  84. data/lib/graphql/execution/input_values.rb +261 -0
  85. data/lib/graphql/execution/interpreter/argument_value.rb +5 -1
  86. data/lib/graphql/execution/interpreter/arguments_cache.rb +5 -10
  87. data/lib/graphql/execution/interpreter/handles_raw_value.rb +6 -0
  88. data/lib/graphql/execution/interpreter/resolve.rb +23 -25
  89. data/lib/graphql/execution/interpreter/runtime/graphql_result.rb +228 -0
  90. data/lib/graphql/execution/interpreter/runtime.rb +365 -435
  91. data/lib/graphql/execution/interpreter.rb +87 -163
  92. data/lib/graphql/execution/lazy.rb +1 -1
  93. data/lib/graphql/execution/load_argument_step.rb +64 -0
  94. data/lib/graphql/execution/lookahead.rb +105 -31
  95. data/lib/graphql/execution/multiplex.rb +7 -6
  96. data/lib/graphql/execution/next.rb +90 -0
  97. data/lib/graphql/execution/prepare_object_step.rb +128 -0
  98. data/lib/graphql/execution/runner.rb +410 -0
  99. data/lib/graphql/execution/selections_step.rb +91 -0
  100. data/lib/graphql/execution.rb +8 -4
  101. data/lib/graphql/execution_error.rb +17 -10
  102. data/lib/graphql/introspection/directive_location_enum.rb +1 -1
  103. data/lib/graphql/introspection/directive_type.rb +7 -3
  104. data/lib/graphql/introspection/dynamic_fields.rb +5 -1
  105. data/lib/graphql/introspection/entry_points.rb +20 -6
  106. data/lib/graphql/introspection/enum_value_type.rb +5 -5
  107. data/lib/graphql/introspection/field_type.rb +13 -5
  108. data/lib/graphql/introspection/input_value_type.rb +21 -13
  109. data/lib/graphql/introspection/schema_type.rb +8 -11
  110. data/lib/graphql/introspection/type_type.rb +64 -28
  111. data/lib/graphql/invalid_name_error.rb +1 -1
  112. data/lib/graphql/invalid_null_error.rb +26 -17
  113. data/lib/graphql/language/block_string.rb +34 -18
  114. data/lib/graphql/language/cache.rb +13 -0
  115. data/lib/graphql/language/comment.rb +18 -0
  116. data/lib/graphql/language/definition_slice.rb +1 -1
  117. data/lib/graphql/language/document_from_schema_definition.rb +90 -61
  118. data/lib/graphql/language/lexer.rb +323 -193
  119. data/lib/graphql/language/nodes.rb +139 -77
  120. data/lib/graphql/language/parser.rb +807 -1985
  121. data/lib/graphql/language/printer.rb +324 -151
  122. data/lib/graphql/language/sanitized_printer.rb +21 -23
  123. data/lib/graphql/language/static_visitor.rb +171 -0
  124. data/lib/graphql/language/visitor.rb +62 -119
  125. data/lib/graphql/language.rb +71 -1
  126. data/lib/graphql/load_application_object_failed_error.rb +5 -1
  127. data/lib/graphql/pagination/array_connection.rb +6 -6
  128. data/lib/graphql/pagination/connection.rb +30 -1
  129. data/lib/graphql/pagination/connections.rb +32 -0
  130. data/lib/graphql/pagination/mongoid_relation_connection.rb +1 -2
  131. data/lib/graphql/query/context/scoped_context.rb +101 -0
  132. data/lib/graphql/query/context.rb +88 -144
  133. data/lib/graphql/query/null_context.rb +15 -18
  134. data/lib/graphql/query/partial.rb +194 -0
  135. data/lib/graphql/query/validation_pipeline.rb +4 -4
  136. data/lib/graphql/query/variable_validation_error.rb +1 -1
  137. data/lib/graphql/query/variables.rb +3 -3
  138. data/lib/graphql/query.rb +135 -81
  139. data/lib/graphql/railtie.rb +16 -6
  140. data/lib/graphql/rake_task.rb +3 -12
  141. data/lib/graphql/rubocop/graphql/base_cop.rb +1 -1
  142. data/lib/graphql/rubocop/graphql/field_type_in_block.rb +144 -0
  143. data/lib/graphql/rubocop/graphql/root_types_in_block.rb +38 -0
  144. data/lib/graphql/rubocop.rb +2 -0
  145. data/lib/graphql/runtime_error.rb +6 -0
  146. data/lib/graphql/schema/addition.rb +26 -13
  147. data/lib/graphql/schema/always_visible.rb +7 -2
  148. data/lib/graphql/schema/argument.rb +78 -14
  149. data/lib/graphql/schema/base_64_encoder.rb +3 -5
  150. data/lib/graphql/schema/build_from_definition.rb +140 -66
  151. data/lib/graphql/schema/directive/flagged.rb +4 -2
  152. data/lib/graphql/schema/directive/one_of.rb +12 -0
  153. data/lib/graphql/schema/directive/specified_by.rb +14 -0
  154. data/lib/graphql/schema/directive.rb +78 -12
  155. data/lib/graphql/schema/enum.rb +110 -27
  156. data/lib/graphql/schema/enum_value.rb +11 -3
  157. data/lib/graphql/schema/field/connection_extension.rb +4 -51
  158. data/lib/graphql/schema/field/scope_extension.rb +19 -7
  159. data/lib/graphql/schema/field.rb +245 -119
  160. data/lib/graphql/schema/field_extension.rb +12 -9
  161. data/lib/graphql/schema/has_single_input_argument.rb +160 -0
  162. data/lib/graphql/schema/input_object.rb +123 -65
  163. data/lib/graphql/schema/interface.rb +60 -16
  164. data/lib/graphql/schema/introspection_system.rb +8 -17
  165. data/lib/graphql/schema/late_bound_type.rb +4 -0
  166. data/lib/graphql/schema/list.rb +8 -4
  167. data/lib/graphql/schema/loader.rb +3 -4
  168. data/lib/graphql/schema/member/base_dsl_methods.rb +18 -12
  169. data/lib/graphql/schema/member/has_arguments.rb +132 -100
  170. data/lib/graphql/schema/member/has_authorization.rb +35 -0
  171. data/lib/graphql/schema/member/has_dataloader.rb +99 -0
  172. data/lib/graphql/schema/member/has_deprecation_reason.rb +15 -0
  173. data/lib/graphql/schema/member/has_directives.rb +5 -5
  174. data/lib/graphql/schema/member/has_fields.rb +121 -17
  175. data/lib/graphql/schema/member/has_interfaces.rb +27 -13
  176. data/lib/graphql/schema/member/has_unresolved_type_error.rb +5 -1
  177. data/lib/graphql/schema/member/has_validators.rb +1 -1
  178. data/lib/graphql/schema/member/relay_shortcuts.rb +1 -1
  179. data/lib/graphql/schema/member/scoped.rb +19 -0
  180. data/lib/graphql/schema/member/type_system_helpers.rb +18 -5
  181. data/lib/graphql/schema/member/validates_input.rb +3 -3
  182. data/lib/graphql/schema/member.rb +6 -0
  183. data/lib/graphql/schema/mutation.rb +7 -0
  184. data/lib/graphql/schema/non_null.rb +1 -1
  185. data/lib/graphql/schema/object.rb +34 -8
  186. data/lib/graphql/schema/printer.rb +9 -7
  187. data/lib/graphql/schema/ractor_shareable.rb +79 -0
  188. data/lib/graphql/schema/relay_classic_mutation.rb +6 -129
  189. data/lib/graphql/schema/resolver.rb +128 -32
  190. data/lib/graphql/schema/scalar.rb +4 -9
  191. data/lib/graphql/schema/subscription.rb +63 -12
  192. data/lib/graphql/schema/timeout.rb +19 -2
  193. data/lib/graphql/schema/type_expression.rb +2 -2
  194. data/lib/graphql/schema/union.rb +2 -2
  195. data/lib/graphql/schema/unique_within_type.rb +1 -1
  196. data/lib/graphql/schema/validator/all_validator.rb +62 -0
  197. data/lib/graphql/schema/validator/required_validator.rb +92 -11
  198. data/lib/graphql/schema/validator.rb +3 -1
  199. data/lib/graphql/schema/visibility/migration.rb +188 -0
  200. data/lib/graphql/schema/visibility/profile.rb +464 -0
  201. data/lib/graphql/schema/visibility/visit.rb +190 -0
  202. data/lib/graphql/schema/visibility.rb +311 -0
  203. data/lib/graphql/schema/warden.rb +275 -103
  204. data/lib/graphql/schema/wrapper.rb +7 -1
  205. data/lib/graphql/schema.rb +954 -212
  206. data/lib/graphql/static_validation/all_rules.rb +3 -3
  207. data/lib/graphql/static_validation/base_visitor.rb +96 -71
  208. data/lib/graphql/static_validation/literal_validator.rb +6 -7
  209. data/lib/graphql/static_validation/rules/argument_literals_are_compatible.rb +2 -2
  210. data/lib/graphql/static_validation/rules/argument_names_are_unique.rb +18 -6
  211. data/lib/graphql/static_validation/rules/arguments_are_defined.rb +6 -2
  212. data/lib/graphql/static_validation/rules/directives_are_defined.rb +6 -3
  213. data/lib/graphql/static_validation/rules/directives_are_in_valid_locations.rb +2 -0
  214. data/lib/graphql/static_validation/rules/fields_are_defined_on_type.rb +14 -3
  215. data/lib/graphql/static_validation/rules/fields_have_appropriate_selections.rb +59 -15
  216. data/lib/graphql/static_validation/rules/fields_will_merge.rb +391 -262
  217. data/lib/graphql/static_validation/rules/fields_will_merge_error.rb +10 -2
  218. data/lib/graphql/static_validation/rules/fragment_spreads_are_possible.rb +6 -6
  219. data/lib/graphql/static_validation/rules/fragment_types_exist.rb +15 -2
  220. data/lib/graphql/static_validation/rules/fragments_are_on_composite_types.rb +1 -1
  221. data/lib/graphql/static_validation/rules/mutation_root_exists.rb +1 -1
  222. data/lib/graphql/static_validation/rules/no_definitions_are_present.rb +1 -1
  223. data/lib/graphql/static_validation/rules/not_single_subscription_error.rb +25 -0
  224. data/lib/graphql/static_validation/rules/query_root_exists.rb +1 -1
  225. data/lib/graphql/static_validation/rules/required_arguments_are_present.rb +28 -8
  226. data/lib/graphql/static_validation/rules/required_input_object_attributes_are_present.rb +5 -5
  227. data/lib/graphql/static_validation/rules/subscription_root_exists_and_single_subscription_selection.rb +26 -0
  228. data/lib/graphql/static_validation/rules/unique_directives_per_location.rb +7 -3
  229. data/lib/graphql/static_validation/rules/variable_default_values_are_correctly_typed.rb +18 -27
  230. data/lib/graphql/static_validation/rules/variable_names_are_unique.rb +1 -1
  231. data/lib/graphql/static_validation/rules/variable_usages_are_allowed.rb +2 -2
  232. data/lib/graphql/static_validation/rules/variables_are_input_types.rb +14 -2
  233. data/lib/graphql/static_validation/validation_context.rb +22 -6
  234. data/lib/graphql/static_validation/validator.rb +9 -1
  235. data/lib/graphql/static_validation.rb +0 -1
  236. data/lib/graphql/subscriptions/action_cable_subscriptions.rb +8 -5
  237. data/lib/graphql/subscriptions/broadcast_analyzer.rb +11 -5
  238. data/lib/graphql/subscriptions/default_subscription_resolve_extension.rb +45 -19
  239. data/lib/graphql/subscriptions/event.rb +22 -4
  240. data/lib/graphql/subscriptions/serialize.rb +3 -1
  241. data/lib/graphql/subscriptions.rb +56 -17
  242. data/lib/graphql/testing/helpers.rb +161 -0
  243. data/lib/graphql/testing/mock_action_cable.rb +111 -0
  244. data/lib/graphql/testing.rb +3 -0
  245. data/lib/graphql/tracing/active_support_notifications_trace.rb +14 -3
  246. data/lib/graphql/tracing/active_support_notifications_tracing.rb +1 -1
  247. data/lib/graphql/tracing/appoptics_trace.rb +11 -3
  248. data/lib/graphql/tracing/appoptics_tracing.rb +9 -2
  249. data/lib/graphql/tracing/appsignal_trace.rb +32 -55
  250. data/lib/graphql/tracing/appsignal_tracing.rb +2 -0
  251. data/lib/graphql/tracing/call_legacy_tracers.rb +66 -0
  252. data/lib/graphql/tracing/data_dog_trace.rb +46 -158
  253. data/lib/graphql/tracing/data_dog_tracing.rb +2 -0
  254. data/lib/graphql/tracing/detailed_trace/active_record_backend.rb +74 -0
  255. data/lib/graphql/tracing/detailed_trace/memory_backend.rb +60 -0
  256. data/lib/graphql/tracing/detailed_trace/redis_backend.rb +72 -0
  257. data/lib/graphql/tracing/detailed_trace.rb +156 -0
  258. data/lib/graphql/tracing/legacy_hooks_trace.rb +75 -0
  259. data/lib/graphql/tracing/legacy_trace.rb +4 -61
  260. data/lib/graphql/tracing/monitor_trace.rb +283 -0
  261. data/lib/graphql/tracing/new_relic_trace.rb +47 -54
  262. data/lib/graphql/tracing/new_relic_tracing.rb +2 -0
  263. data/lib/graphql/tracing/notifications_trace.rb +184 -34
  264. data/lib/graphql/tracing/notifications_tracing.rb +2 -0
  265. data/lib/graphql/tracing/null_trace.rb +9 -0
  266. data/lib/graphql/tracing/perfetto_trace/trace.proto +141 -0
  267. data/lib/graphql/tracing/perfetto_trace/trace_pb.rb +33 -0
  268. data/lib/graphql/tracing/perfetto_trace.rb +864 -0
  269. data/lib/graphql/tracing/platform_trace.rb +5 -0
  270. data/lib/graphql/tracing/platform_tracing.rb +3 -1
  271. data/lib/graphql/tracing/{prometheus_tracing → prometheus_trace}/graphql_collector.rb +5 -1
  272. data/lib/graphql/tracing/prometheus_trace.rb +72 -68
  273. data/lib/graphql/tracing/prometheus_tracing.rb +2 -0
  274. data/lib/graphql/tracing/scout_trace.rb +32 -55
  275. data/lib/graphql/tracing/scout_tracing.rb +2 -0
  276. data/lib/graphql/tracing/sentry_trace.rb +82 -0
  277. data/lib/graphql/tracing/statsd_trace.rb +33 -41
  278. data/lib/graphql/tracing/statsd_tracing.rb +2 -0
  279. data/lib/graphql/tracing/trace.rb +118 -1
  280. data/lib/graphql/tracing.rb +31 -28
  281. data/lib/graphql/type_kinds.rb +2 -1
  282. data/lib/graphql/types/iso_8601_duration.rb +77 -0
  283. data/lib/graphql/types/relay/connection_behaviors.rb +45 -3
  284. data/lib/graphql/types/relay/edge_behaviors.rb +19 -1
  285. data/lib/graphql/types/relay/has_node_field.rb +13 -8
  286. data/lib/graphql/types/relay/has_nodes_field.rb +13 -8
  287. data/lib/graphql/types/relay/node_behaviors.rb +13 -2
  288. data/lib/graphql/types/relay/page_info_behaviors.rb +4 -0
  289. data/lib/graphql/types.rb +18 -10
  290. data/lib/graphql/unauthorized_enum_value_error.rb +13 -0
  291. data/lib/graphql/unauthorized_error.rb +9 -1
  292. data/lib/graphql/version.rb +1 -1
  293. data/lib/graphql.rb +69 -54
  294. data/readme.md +12 -2
  295. metadata +236 -40
  296. data/lib/graphql/analysis/ast/analyzer.rb +0 -84
  297. data/lib/graphql/analysis/ast/field_usage.rb +0 -57
  298. data/lib/graphql/analysis/ast/max_query_complexity.rb +0 -22
  299. data/lib/graphql/analysis/ast/max_query_depth.rb +0 -22
  300. data/lib/graphql/analysis/ast/query_complexity.rb +0 -230
  301. data/lib/graphql/analysis/ast/query_depth.rb +0 -55
  302. data/lib/graphql/analysis/ast/visitor.rb +0 -276
  303. data/lib/graphql/analysis/ast.rb +0 -81
  304. data/lib/graphql/backtrace/inspect_result.rb +0 -50
  305. data/lib/graphql/backtrace/trace.rb +0 -96
  306. data/lib/graphql/backtrace/tracer.rb +0 -80
  307. data/lib/graphql/deprecation.rb +0 -9
  308. data/lib/graphql/filter.rb +0 -59
  309. data/lib/graphql/language/parser.y +0 -560
  310. data/lib/graphql/language/token.rb +0 -34
  311. data/lib/graphql/schema/base_64_bp.rb +0 -26
  312. data/lib/graphql/schema/invalid_type_error.rb +0 -7
  313. data/lib/graphql/schema/null_mask.rb +0 -11
  314. data/lib/graphql/static_validation/rules/subscription_root_exists.rb +0 -17
  315. data/lib/graphql/static_validation/type_stack.rb +0 -216
  316. data/lib/graphql/subscriptions/instrumentation.rb +0 -28
@@ -3,7 +3,7 @@ module GraphQL
3
3
  module StaticValidation
4
4
  # Default rules for {GraphQL::StaticValidation::Validator}
5
5
  #
6
- # Order is important here. Some validators return {GraphQL::Language::Visitor::SKIP}
6
+ # Order is important here. Some validators skip later hooks.
7
7
  # which stops the visit on that node. That way it doesn't try to find fields on types that
8
8
  # don't exist, etc.
9
9
  ALL_RULES = [
@@ -34,9 +34,9 @@ module GraphQL
34
34
  GraphQL::StaticValidation::VariableUsagesAreAllowed,
35
35
  GraphQL::StaticValidation::MutationRootExists,
36
36
  GraphQL::StaticValidation::QueryRootExists,
37
- GraphQL::StaticValidation::SubscriptionRootExists,
37
+ GraphQL::StaticValidation::SubscriptionRootExistsAndSingleSubscriptionSelection,
38
38
  GraphQL::StaticValidation::InputObjectNamesAreUnique,
39
39
  GraphQL::StaticValidation::OneOfInputObjectsAreValid,
40
- ]
40
+ ].freeze
41
41
  end
42
42
  end
@@ -1,27 +1,29 @@
1
1
  # frozen_string_literal: true
2
2
  module GraphQL
3
3
  module StaticValidation
4
- class BaseVisitor < GraphQL::Language::Visitor
4
+ class BaseVisitor < GraphQL::Language::StaticVisitor
5
5
  def initialize(document, context)
6
6
  @path = []
7
- @object_types = []
8
- @directives = []
9
- @field_definitions = []
10
- @argument_definitions = []
11
- @directive_definitions = []
7
+ @path_depth = 0
8
+ @current_object_type = nil
9
+ @parent_object_type = nil
10
+ @current_field_definition = nil
11
+ @current_argument_definition = nil
12
+ @parent_argument_definition = nil
13
+ @current_directive_definition = nil
12
14
  @context = context
15
+ @types = context.query.types
13
16
  @schema = context.schema
17
+ @inline_fragment_paths = {}
18
+ @field_unwrapped_types = {}.compare_by_identity
14
19
  super(document)
15
20
  end
16
21
 
17
22
  attr_reader :context
18
23
 
19
- # @return [Array<GraphQL::ObjectType>] Types whose scope we've entered
20
- attr_reader :object_types
21
-
22
24
  # @return [Array<String>] The nesting of the current position in the AST
23
25
  def path
24
- @path.dup
26
+ @path[0, @path_depth]
25
27
  end
26
28
 
27
29
  # Build a class to visit the AST and perform validation,
@@ -54,86 +56,125 @@ module GraphQL
54
56
  module ContextMethods
55
57
  def on_operation_definition(node, parent)
56
58
  object_type = @schema.root_type_for_operation(node.operation_type)
57
- push_type(object_type)
58
- @path.push("#{node.operation_type}#{node.name ? " #{node.name}" : ""}")
59
+ prev_parent_ot = @parent_object_type
60
+ @parent_object_type = @current_object_type
61
+ @current_object_type = object_type
62
+ @path[@path_depth] = "#{node.operation_type}#{node.name ? " #{node.name}" : ""}"
63
+ @path_depth += 1
59
64
  super
60
- @object_types.pop
61
- @path.pop
65
+ @current_object_type = @parent_object_type
66
+ @parent_object_type = prev_parent_ot
67
+ @path_depth -= 1
62
68
  end
63
69
 
64
70
  def on_fragment_definition(node, parent)
65
- on_fragment_with_type(node) do
66
- @path.push("fragment #{node.name}")
67
- super
71
+ object_type = if node.type
72
+ @types.type(node.type.name)
73
+ else
74
+ @current_object_type
68
75
  end
76
+ prev_parent_ot = @parent_object_type
77
+ @parent_object_type = @current_object_type
78
+ @current_object_type = object_type
79
+ @path[@path_depth] = "fragment #{node.name}"
80
+ @path_depth += 1
81
+ super
82
+ @current_object_type = @parent_object_type
83
+ @parent_object_type = prev_parent_ot
84
+ @path_depth -= 1
69
85
  end
70
86
 
87
+ INLINE_FRAGMENT_NO_TYPE = "..."
88
+
71
89
  def on_inline_fragment(node, parent)
72
- on_fragment_with_type(node) do
73
- @path.push("...#{node.type ? " on #{node.type.to_query_string}" : ""}")
74
- super
90
+ if node.type
91
+ object_type = @types.type(node.type.name)
92
+ @path[@path_depth] = @inline_fragment_paths[node.type.name] ||= -"... on #{node.type.to_query_string}"
93
+ @path_depth += 1
94
+ else
95
+ object_type = @current_object_type
96
+ @path[@path_depth] = INLINE_FRAGMENT_NO_TYPE
97
+ @path_depth += 1
75
98
  end
99
+ prev_parent_ot = @parent_object_type
100
+ @parent_object_type = @current_object_type
101
+ @current_object_type = object_type
102
+ super
103
+ @current_object_type = @parent_object_type
104
+ @parent_object_type = prev_parent_ot
105
+ @path_depth -= 1
76
106
  end
77
107
 
78
108
  def on_field(node, parent)
79
- parent_type = @object_types.last
80
- field_definition = @schema.get_field(parent_type, node.name, @context.query.context)
81
- @field_definitions.push(field_definition)
82
- if !field_definition.nil?
83
- next_object_type = field_definition.type.unwrap
84
- push_type(next_object_type)
109
+ parent_type = @current_object_type
110
+ field_definition = @types.field(parent_type, node.name)
111
+ prev_field_definition = @current_field_definition
112
+ @current_field_definition = field_definition
113
+ prev_parent_ot = @parent_object_type
114
+ @parent_object_type = @current_object_type
115
+ if field_definition
116
+ @current_object_type = @field_unwrapped_types[field_definition] ||= field_definition.type.unwrap
85
117
  else
86
- push_type(nil)
118
+ @current_object_type = nil
87
119
  end
88
- @path.push(node.alias || node.name)
120
+ @path[@path_depth] = node.alias || node.name
121
+ @path_depth += 1
89
122
  super
90
- @field_definitions.pop
91
- @object_types.pop
92
- @path.pop
123
+ @current_field_definition = prev_field_definition
124
+ @current_object_type = @parent_object_type
125
+ @parent_object_type = prev_parent_ot
126
+ @path_depth -= 1
93
127
  end
94
128
 
95
129
  def on_directive(node, parent)
96
130
  directive_defn = @context.schema_directives[node.name]
97
- @directive_definitions.push(directive_defn)
131
+ prev_directive_definition = @current_directive_definition
132
+ @current_directive_definition = directive_defn
98
133
  super
99
- @directive_definitions.pop
134
+ @current_directive_definition = prev_directive_definition
100
135
  end
101
136
 
102
137
  def on_argument(node, parent)
103
- argument_defn = if (arg = @argument_definitions.last)
138
+ argument_defn = if (arg = @current_argument_definition)
104
139
  arg_type = arg.type.unwrap
105
140
  if arg_type.kind.input_object?
106
- @context.warden.get_argument(arg_type, node.name)
141
+ @types.argument(arg_type, node.name)
107
142
  else
108
143
  nil
109
144
  end
110
- elsif (directive_defn = @directive_definitions.last)
111
- @context.warden.get_argument(directive_defn, node.name)
112
- elsif (field_defn = @field_definitions.last)
113
- @context.warden.get_argument(field_defn, node.name)
145
+ elsif (directive_defn = @current_directive_definition)
146
+ @types.argument(directive_defn, node.name)
147
+ elsif (field_defn = @current_field_definition)
148
+ @types.argument(field_defn, node.name)
114
149
  else
115
150
  nil
116
151
  end
117
152
 
118
- @argument_definitions.push(argument_defn)
119
- @path.push(node.name)
153
+ prev_parent = @parent_argument_definition
154
+ @parent_argument_definition = @current_argument_definition
155
+ @current_argument_definition = argument_defn
156
+ @path[@path_depth] = node.name
157
+ @path_depth += 1
120
158
  super
121
- @argument_definitions.pop
122
- @path.pop
159
+ @current_argument_definition = @parent_argument_definition
160
+ @parent_argument_definition = prev_parent
161
+ @path_depth -= 1
123
162
  end
124
163
 
125
164
  def on_fragment_spread(node, parent)
126
- @path.push("... #{node.name}")
165
+ @path[@path_depth] = "... #{node.name}"
166
+ @path_depth += 1
127
167
  super
128
- @path.pop
168
+ @path_depth -= 1
129
169
  end
130
170
 
131
171
  def on_input_object(node, parent)
132
- arg_defn = @argument_definitions.last
172
+ arg_defn = @current_argument_definition
133
173
  if arg_defn && arg_defn.type.list?
134
- @path.push(parent.children.index(node))
174
+ @path[@path_depth] = parent.children.index(node)
175
+ @path_depth += 1
135
176
  super
136
- @path.pop
177
+ @path_depth -= 1
137
178
  else
138
179
  super
139
180
  end
@@ -141,48 +182,32 @@ module GraphQL
141
182
 
142
183
  # @return [GraphQL::BaseType] The current object type
143
184
  def type_definition
144
- @object_types.last
185
+ @current_object_type
145
186
  end
146
187
 
147
188
  # @return [GraphQL::BaseType] The type which the current type came from
148
189
  def parent_type_definition
149
- @object_types[-2]
190
+ @parent_object_type
150
191
  end
151
192
 
152
193
  # @return [GraphQL::Field, nil] The most-recently-entered GraphQL::Field, if currently inside one
153
194
  def field_definition
154
- @field_definitions.last
195
+ @current_field_definition
155
196
  end
156
197
 
157
198
  # @return [GraphQL::Directive, nil] The most-recently-entered GraphQL::Directive, if currently inside one
158
199
  def directive_definition
159
- @directive_definitions.last
200
+ @current_directive_definition
160
201
  end
161
202
 
162
203
  # @return [GraphQL::Argument, nil] The most-recently-entered GraphQL::Argument, if currently inside one
163
204
  def argument_definition
164
- # Don't get the _last_ one because that's the current one.
165
- # Get the second-to-last one, which is the parent of the current one.
166
- @argument_definitions[-2]
205
+ # Return the parent argument definition (not the current one).
206
+ @parent_argument_definition
167
207
  end
168
208
 
169
209
  private
170
210
 
171
- def on_fragment_with_type(node)
172
- object_type = if node.type
173
- @context.warden.get_type(node.type.name)
174
- else
175
- @object_types.last
176
- end
177
- push_type(object_type)
178
- yield(node)
179
- @object_types.pop
180
- @path.pop
181
- end
182
-
183
- def push_type(t)
184
- @object_types.push(t)
185
- end
186
211
  end
187
212
 
188
213
  private
@@ -191,7 +216,7 @@ module GraphQL
191
216
  if @context.too_many_errors?
192
217
  throw :too_many_validation_errors
193
218
  end
194
- error.path ||= (path || @path.dup)
219
+ error.path ||= (path || @path[0, @path_depth])
195
220
  context.errors << error
196
221
  end
197
222
 
@@ -5,7 +5,7 @@ module GraphQL
5
5
  class LiteralValidator
6
6
  def initialize(context:)
7
7
  @context = context
8
- @warden = context.warden
8
+ @types = context.types
9
9
  @invalid_response = GraphQL::Query::InputValidationResult.new(valid: false, problems: [])
10
10
  @valid_response = GraphQL::Query::InputValidationResult.new(valid: true, problems: [])
11
11
  end
@@ -109,9 +109,9 @@ module GraphQL
109
109
  def required_input_fields_are_present(type, ast_node)
110
110
  # TODO - would be nice to use these to create an error message so the caller knows
111
111
  # that required fields are missing
112
- required_field_names = @warden.arguments(type)
113
- .select { |argument| argument.type.kind.non_null? && @warden.get_argument(type, argument.name) }
114
- .map(&:name)
112
+ required_field_names = @types.arguments(type)
113
+ .select { |argument| argument.type.kind.non_null? && !argument.default_value? }
114
+ .map!(&:name)
115
115
 
116
116
  present_field_names = ast_node.arguments.map(&:name)
117
117
  missing_required_field_names = required_field_names - present_field_names
@@ -119,10 +119,9 @@ module GraphQL
119
119
  missing_required_field_names.empty? ? @valid_response : @invalid_response
120
120
  else
121
121
  results = missing_required_field_names.map do |name|
122
- arg_type = @warden.get_argument(type, name).type
122
+ arg_type = @types.argument(type, name).type
123
123
  recursively_validate(GraphQL::Language::Nodes::NullValue.new(name: name), arg_type)
124
124
  end
125
-
126
125
  if type.one_of? && ast_node.arguments.size != 1
127
126
  results << Query::InputValidationResult.from_problem("`#{type.graphql_name}` is a OneOf type, so only one argument may be given (instead of #{ast_node.arguments.size})")
128
127
  end
@@ -132,7 +131,7 @@ module GraphQL
132
131
 
133
132
  def present_input_field_values_are_valid(type, ast_node)
134
133
  results = ast_node.arguments.map do |value|
135
- field = @warden.get_argument(type, value.name)
134
+ field = @types.argument(type, value.name)
136
135
  # we want to call validate on an argument even if it's an invalid one
137
136
  # so that our raise exception is on it instead of the entire InputObject
138
137
  field_type = field && field.type
@@ -12,10 +12,10 @@ module GraphQL
12
12
  return
13
13
  end
14
14
 
15
- if @context.schema.error_bubbling || context.errors.none? { |err| err.path.take(@path.size) == @path }
15
+ if @context.schema.error_bubbling || context.errors.none? { |err| err.path.take(@path_depth) == @path[0, @path_depth] }
16
16
  parent_defn = parent_definition(parent)
17
17
 
18
- if parent_defn && (arg_defn = context.warden.get_argument(parent_defn, node.name))
18
+ if parent_defn && (arg_defn = @types.argument(parent_defn, node.name))
19
19
  validation_result = context.validate_literal(node.value, arg_defn.type)
20
20
  if !validation_result.valid?
21
21
  kind_of_node = node_type(parent)
@@ -16,12 +16,24 @@ module GraphQL
16
16
 
17
17
  def validate_arguments(node)
18
18
  argument_defns = node.arguments
19
- if argument_defns.any?
20
- args_by_name = Hash.new { |h, k| h[k] = [] }
21
- argument_defns.each { |a| args_by_name[a.name] << a }
22
- args_by_name.each do |name, defns|
23
- if defns.size > 1
24
- add_error(GraphQL::StaticValidation::ArgumentNamesAreUniqueError.new("There can be only one argument named \"#{name}\"", nodes: defns, name: name))
19
+ if argument_defns.size > 1
20
+ seen = {}
21
+ argument_defns.each do |a|
22
+ name = a.name
23
+ if seen.key?(name)
24
+ prev = seen[name]
25
+ if prev.is_a?(Array)
26
+ prev << a
27
+ else
28
+ seen[name] = [prev, a]
29
+ end
30
+ else
31
+ seen[name] = a
32
+ end
33
+ end
34
+ seen.each do |name, val|
35
+ if val.is_a?(Array)
36
+ add_error(GraphQL::StaticValidation::ArgumentNamesAreUniqueError.new("There can be only one argument named \"#{name}\"", nodes: val, name: name))
25
37
  end
26
38
  end
27
39
  end
@@ -5,13 +5,17 @@ module GraphQL
5
5
  def on_argument(node, parent)
6
6
  parent_defn = parent_definition(parent)
7
7
 
8
- if parent_defn && context.warden.get_argument(parent_defn, node.name)
8
+ if parent_defn && @types.argument(parent_defn, node.name)
9
9
  super
10
10
  elsif parent_defn
11
11
  kind_of_node = node_type(parent)
12
12
  error_arg_name = parent_name(parent, parent_defn)
13
+ suggestion = if @schema.did_you_mean
14
+ arg_names = context.types.arguments(parent_defn).map(&:graphql_name)
15
+ context.did_you_mean_suggestion(node.name, arg_names)
16
+ end
13
17
  add_error(GraphQL::StaticValidation::ArgumentsAreDefinedError.new(
14
- "#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'",
18
+ "#{kind_of_node} '#{error_arg_name}' doesn't accept argument '#{node.name}'#{suggestion}",
15
19
  nodes: node,
16
20
  name: error_arg_name,
17
21
  type: kind_of_node,
@@ -4,15 +4,18 @@ module GraphQL
4
4
  module DirectivesAreDefined
5
5
  def initialize(*)
6
6
  super
7
- @directive_names = context.warden.directives.map(&:graphql_name)
8
7
  end
9
8
 
10
9
  def on_directive(node, parent)
11
- if !@directive_names.include?(node.name)
10
+ if !@types.directive_exists?(node.name)
12
11
  @directives_are_defined_errors_by_name ||= {}
13
12
  error = @directives_are_defined_errors_by_name[node.name] ||= begin
13
+ suggestion = if @schema.did_you_mean
14
+ @directive_names ||= @types.directives.map(&:graphql_name)
15
+ context.did_you_mean_suggestion(node.name, @directive_names)
16
+ end
14
17
  err = GraphQL::StaticValidation::DirectivesAreDefinedError.new(
15
- "Directive @#{node.name} is not defined",
18
+ "Directive @#{node.name} is not defined#{suggestion}",
16
19
  nodes: [],
17
20
  directive: node.name
18
21
  )
@@ -19,6 +19,7 @@ module GraphQL
19
19
  GraphQL::Schema::Directive::FRAGMENT_DEFINITION => "fragment definitions",
20
20
  GraphQL::Schema::Directive::FRAGMENT_SPREAD => "fragment spreads",
21
21
  GraphQL::Schema::Directive::INLINE_FRAGMENT => "inline fragments",
22
+ GraphQL::Schema::Directive::VARIABLE_DEFINITION => "variable definitions",
22
23
  }
23
24
 
24
25
  SIMPLE_LOCATIONS = {
@@ -26,6 +27,7 @@ module GraphQL
26
27
  Nodes::InlineFragment => GraphQL::Schema::Directive::INLINE_FRAGMENT,
27
28
  Nodes::FragmentSpread => GraphQL::Schema::Directive::FRAGMENT_SPREAD,
28
29
  Nodes::FragmentDefinition => GraphQL::Schema::Directive::FRAGMENT_DEFINITION,
30
+ Nodes::VariableDefinition => GraphQL::Schema::Directive::VARIABLE_DEFINITION,
29
31
  }
30
32
 
31
33
  SIMPLE_LOCATION_NODES = SIMPLE_LOCATIONS.keys
@@ -3,8 +3,8 @@ module GraphQL
3
3
  module StaticValidation
4
4
  module FieldsAreDefinedOnType
5
5
  def on_field(node, parent)
6
- parent_type = @object_types[-2]
7
- field = context.warden.get_field(parent_type, node.name)
6
+ parent_type = @parent_object_type
7
+ field = context.query.types.field(parent_type, node.name)
8
8
 
9
9
  if field.nil?
10
10
  if parent_type.kind.union?
@@ -14,8 +14,12 @@ module GraphQL
14
14
  node_name: parent_type.graphql_name
15
15
  ))
16
16
  else
17
+ suggestion = if @schema.did_you_mean
18
+ context.did_you_mean_suggestion(node.name, possible_fields(context, parent_type))
19
+ end
20
+ message = "Field '#{node.name}' doesn't exist on type '#{parent_type.graphql_name}'#{suggestion}"
17
21
  add_error(GraphQL::StaticValidation::FieldsAreDefinedOnTypeError.new(
18
- "Field '#{node.name}' doesn't exist on type '#{parent_type.graphql_name}'",
22
+ message,
19
23
  nodes: node,
20
24
  field: node.name,
21
25
  type: parent_type.graphql_name
@@ -25,6 +29,13 @@ module GraphQL
25
29
  super
26
30
  end
27
31
  end
32
+
33
+ private
34
+
35
+ def possible_fields(context, parent_type)
36
+ return EmptyObjects::EMPTY_ARRAY if parent_type.kind.leaf?
37
+ context.types.fields(parent_type).map(&:graphql_name)
38
+ end
28
39
  end
29
40
  end
30
41
  end
@@ -7,8 +7,7 @@ module GraphQL
7
7
  include GraphQL::StaticValidation::Error::ErrorHelper
8
8
 
9
9
  def on_field(node, parent)
10
- field_defn = field_definition
11
- if validate_field_selections(node, field_defn.type.unwrap)
10
+ if validate_field_selections(node, @current_object_type)
12
11
  super
13
12
  end
14
13
  end
@@ -23,24 +22,69 @@ module GraphQL
23
22
 
24
23
 
25
24
  def validate_field_selections(ast_node, resolved_type)
25
+ # Fast paths for the two most common cases:
26
+ # 1. Leaf type with no selections (scalars, enums) — most fields
27
+ # 2. Non-leaf type with selections (objects, interfaces)
28
+ if resolved_type
29
+ if ast_node.selections.empty?
30
+ return true if resolved_type.kind.leaf?
31
+ else
32
+ return true unless resolved_type.kind.leaf?
33
+ end
34
+ end
35
+
26
36
  msg = if resolved_type.nil?
27
37
  nil
28
- elsif resolved_type.kind.scalar? && ast_node.selections.any?
29
- selection_strs = ast_node.selections.map do |n|
30
- case n
31
- when GraphQL::Language::Nodes::InlineFragment
32
- "\"... on #{n.type.name} { ... }\""
33
- when GraphQL::Language::Nodes::Field
34
- "\"#{n.name}\""
35
- when GraphQL::Language::Nodes::FragmentSpread
36
- "\"#{n.name}\""
38
+ elsif resolved_type.kind.leaf?
39
+ if !ast_node.selections.empty?
40
+ selection_strs = ast_node.selections.map do |n|
41
+ case n
42
+ when GraphQL::Language::Nodes::InlineFragment
43
+ "\"... on #{n.type.name} { ... }\""
44
+ when GraphQL::Language::Nodes::Field
45
+ "\"#{n.name}\""
46
+ when GraphQL::Language::Nodes::FragmentSpread
47
+ "\"#{n.name}\""
48
+ else
49
+ raise "Invariant: unexpected selection node: #{n}"
50
+ end
51
+ end
52
+ "Selections can't be made on #{resolved_type.kind.name.sub("_", " ").downcase}s (%{node_name} returns #{resolved_type.graphql_name} but has selections [#{selection_strs.join(", ")}])"
53
+ else
54
+ nil
55
+ end
56
+ elsif ast_node.selections.empty?
57
+ return_validation_error = true
58
+ legacy_invalid_empty_selection_result = nil
59
+ if !resolved_type.kind.fields?
60
+ case @schema.allow_legacy_invalid_empty_selections_on_union
61
+ when true
62
+ legacy_invalid_empty_selection_result = @schema.legacy_invalid_empty_selections_on_union_with_type(@context.query, resolved_type)
63
+ case legacy_invalid_empty_selection_result
64
+ when :return_validation_error
65
+ # keep `return_validation_error = true`
66
+ when String
67
+ return_validation_error = false
68
+ # the string is returned below
69
+ when nil
70
+ # No error:
71
+ return_validation_error = false
72
+ legacy_invalid_empty_selection_result = nil
73
+ else
74
+ raise GraphQL::InvariantError, "Unexpected return value from legacy_invalid_empty_selections_on_union_with_type, must be `:return_validation_error`, String, or nil (got: #{legacy_invalid_empty_selection_result.inspect})"
75
+ end
76
+ when false
77
+ # pass -- error below
37
78
  else
38
- raise "Invariant: unexpected selection node: #{n}"
79
+ return_validation_error = false
80
+ @context.query.logger.warn("Unions require selections but #{ast_node.alias || ast_node.name} (#{resolved_type.graphql_name}) doesn't have any. This will fail with a validation error on a future GraphQL-Ruby version. More info: https://graphql-ruby.org/api-doc/#{GraphQL::VERSION}/GraphQL/Schema.html#allow_legacy_invalid_empty_selections_on_union-class_method")
39
81
  end
40
82
  end
41
- "Selections can't be made on scalars (%{node_name} returns #{resolved_type.graphql_name} but has selections [#{selection_strs.join(", ")}])"
42
- elsif resolved_type.kind.fields? && ast_node.selections.empty?
43
- "Field must have selections (%{node_name} returns #{resolved_type.graphql_name} but has no selections. Did you mean '#{ast_node.name} { ... }'?)"
83
+ if return_validation_error
84
+ "Field must have selections (%{node_name} returns #{resolved_type.graphql_name} but has no selections. Did you mean '#{ast_node.name} { ... }'?)"
85
+ else
86
+ legacy_invalid_empty_selection_result
87
+ end
44
88
  else
45
89
  nil
46
90
  end